/***************************************************************************
 *   Copyright (C) 2009 by maarten,,,   *
 *   maarten@maarten-laptop   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "cgpanel/raytracer/raytracer.h"

#include "scenemanager/camera/camera.h"
#include "cgpanel/raytracer/ray.h"
#include "objects/plane.h" // = consisted of three vertices (triangle)
#include "objects/vertex.h"
#include "cgpanel/floatmatrix.h"
#include "cgpanel/cgpanel.h"
#include "scenemanager/scene/shape.h"
#include "scenemanager/scenemanager.h"
#include "objects/objectlist.h"
#include "objects/object.h"
#include "scenemanager/scene/scene.h"
#include "scenemanager/textures/texture.h"
#include "scenemanager/light/pointlight.h"
#include "objects/plane.h"
#include "cgpanel/raytracer/bvhtree.h"
#include "cgpanel/raytracer/bvhnode.h"
#include "cgpanel/raytracer/hitrecord.h"
#include "objects/objecttexture.h"
#include <stdio.h>
#include <stdlib.h>

#include <iostream>
using namespace std;

#include <QRgb>

using namespace utils;
using namespace object;

namespace draw {

RayTracer::RayTracer(int w, int h, int tempAA)
{
    antialiasing = tempAA;
    m_rayMatrix = new RayMatrix(h*antialiasing,w*antialiasing);
    m_width = w;
    m_height = h;
    aMat = new FloatMatrix();
    bMat = new object::Vertex();
    bvhTree = new BVHTree();
    m_hitRecord = new HitRecord();
      
    fake = new HitRecord();
    color = new Vertex();
    scaledV = new Vertex();
    lightDir = new Vertex();
    
    for(int i =0; i < MAX_REC_LEVEL; ++i){
      m_recursiveColors.push_back(new QColor());
      m_realPosXYZ.push_back(new Vertex());
    }
    
    rec_level = 0;
    
    isDiffuse = false;
    isPhong = false;
    shadowRay = new Ray();
    m_reflect = false;
    m_refract = false;
    
}

void RayTracer::setShapes(QVector<object::Plane*> &planesToDraw,
scene::SceneManager * sceneMgr )
{
  
  m_planesToDraw = planesToDraw;
  m_sceneMgr = sceneMgr;
  for(int i = 0; i < m_sceneMgr->getScene()->getNrOfLights(); ++i)
    pLights.push_back((scene::PointLight*)(m_sceneMgr->getScene()->light(i)));
  
  bvhTree->initialize(m_planesToDraw);
  cout << "finito" << endl;
}

Vertex* RayTracer::getRayDirection(int i, int j){
  return m_rayMatrix->getRay(i*antialiasing,j*antialiasing)->direction();
}

Vertex* RayTracer::getRayDirection(int i, int j, int p, int q){
  return m_rayMatrix->getRay(i*antialiasing+p,j*antialiasing+q)->direction();
}

RayTracer::~RayTracer()
{
  cout << "Deleting raymatrix...\n";
  if(m_rayMatrix != NULL)
    delete m_rayMatrix;
  
  cout << "Deleting A matrix...\n";
  if(aMat != NULL)
    delete aMat;
  
  cout << "Deleting B matrix...\n";
  if(bMat != NULL)
    delete bMat;

  
  cout << "Deleting bvhtree in raytracer...\n";
  if(bvhTree != NULL)
    delete bvhTree;
  
  delete color;
  delete scaledV;
  
  delete m_s1;
  delete m_s2;
  
  //delete realPosXYZ;
  delete lightDir;
}

void RayTracer::initViewSubFase(int i, int j, int p, int q)
{
    Vertex * s3 = new Vertex(0,0,0);
    FloatMatrix  *tempS;
    Vertex *sMatrix;
    float randomX = 0;
    float randomY = 0;
    
    if(p==0 && q==0){
      randomX = (float)antialiasing/2.0;
      randomY = (float)antialiasing/2.0;
    }else{
      randomX = ((rand() %1000)/1000.0 +(float)p)/(float)antialiasing;
      randomY = ((rand() %1000)/1000.0 +(float)q)/(float)antialiasing;
    }
    s3->setX(-1.0 + (2.0)*((float)i + (randomX))/m_width);
    s3->setY(-1.0 + (2.0)*((float)j + (randomY))/m_height);
    s3->setZ(-m_cam->zDistance());
    
    tempS = m_s1->multiplyWith(m_s2);
    sMatrix = tempS->multiplyWith(s3);
    
    //cout << "debug: fase1" << endl;
    m_rayMatrix->setRay(
                    m_rayMatrix->rows()-q-1-j*antialiasing, 
                    i*antialiasing+p,
                    m_cam->position(),sMatrix);
    //cout << "debug: fase2" << endl;
    delete tempS;                      
    delete sMatrix;
    delete s3;
}
void RayTracer::initViewRays(scene::Camera *cam)
{
    // algoritme p.204
    m_cam = cam;
    /****************************************
    * set s1
    *****************************************/
    m_s1 = new FloatMatrix();

    m_s1->setIdentity();
    
    m_s1->seta3(cam->position()->x());
    m_s1->setb3(cam->position()->y());
    m_s1->setc3(cam->position()->z());

    /****************************************
    * set s2
    *****************************************/
    m_s2 = new FloatMatrix();
    Vertex *W = cam->direction()->negative();
    W->setH(0);
    W->normalize();
    Vertex *U = cam->up()->vecProd(W);
    U->normalize();
    Vertex * V = W->vecProd(U);
    
        
    m_s2->seta0(U->x());
    m_s2->setb0(U->y());
    m_s2->setc0(U->z());

    m_s2->seta1(V->x());
    m_s2->setb1(V->y());
    m_s2->setc1(V->z());

    m_s2->seta2(W->x());
    m_s2->setb2(W->y());
    m_s2->setc2(W->z());
    m_s2->setd3(1);

     /****************************************
    * set variables for calculations
    *****************************************/   
    cam->computeViewPlane(m_width,m_height); 
     
    float lengte = 2; //
    float l = -lengte/(float)2;
    float r = lengte/(float)2;
    float b = l;
    float t = r;
    float m_focalDistance = m_cam->zDistance();
    float Nx = m_width;
    float Ny = m_height;

    /****************************************
    * set s3
    *****************************************/

    for(int i = 0; i < Nx; ++i){
      for(int j = 0; j < Ny; ++j){
        for(float p = 0; p < antialiasing;++p)
          for(float q = 0; q < antialiasing;++q){
            initViewSubFase(i,j,p,q);
          }

        }
    }

}



bool RayTracer::isShadow(object::Vertex *start, object::Vertex *end)
{ 
  shadowRay->init(start,end);
  return bvhTree->getRoot()->hit(shadowRay,shadowRay->direction()->getLength());
}

bool RayTracer::rayColor(Ray * ray)
{  
    if( bvhTree->getRoot()->hit(ray, &m_hitRecord) )
    { 
        float totalColorX = 0;
        float totalColorY = 0;
        float totalColorZ = 0;
        //lastAlpha= m_hitRecord->alpha();
       
        // getSurfaceNormal, do not delete!!!
        surfNorm = m_hitRecord->getPlane()
          ->getSurfaceNormal(m_hitRecord->beta(),m_hitRecord->gamma());
        float scaling = m_hitRecord->t();
        scaledV->setX(ray->direction()->x()*scaling);
        scaledV->setY(ray->direction()->y()*scaling);
        scaledV->setZ(ray->direction()->z()*scaling);
      
        m_realPosXYZ[rec_level]->setX(ray->origin()->x() + scaledV->x());
        m_realPosXYZ[rec_level]->setY(ray->origin()->y() + scaledV->y());
        m_realPosXYZ[rec_level]->setZ(ray->origin()->z() + scaledV->z());
        m_realPosXYZ[rec_level]->setH(ray->origin()->h() + scaledV->h());

        materialType = 0;
        isPhong = false;
        isDiffuse = false;
        m_reflect = false;
        m_refract = false;
        
        if(m_hitRecord->getPlane()->getMat() != NULL)
        {
            mainMaterial = m_hitRecord->getPlane()->getMat();

            materialType = m_hitRecord->getPlane()->getMat()->type();
            m_reflect = m_hitRecord->getPlane()->getMat()->reflect();
            m_refract = m_hitRecord->getPlane()->getMat()->refract();
            
            if(m_reflect|| m_refract)
              m_specColor = m_hitRecord->getPlane()->getMat()->specColor();
               
            if(mainMaterial->glossy())
            {
                m_glossy = true;
                m_glossyFactor = mainMaterial->glossyFactor();
                //cout << "glossyfactor:" << m_glossyFactor << endl;
                //cin.get();
            }
            
            if(materialType==0){
                diffuseMaterial = mainMaterial;
            }
            else if(materialType==1){
                shininess = m_hitRecord->getPlane()->getMat()->shinyExp();
                phongMaterial = mainMaterial;
            }
            else if(materialType == 2){
                if(mainMaterial->getMat1()->type() == 0){
                  diffuseMaterial = mainMaterial->getMat1();
                  phongMaterial = mainMaterial->getMat2();
                }else{
                  phongMaterial = mainMaterial->getMat1();
                  diffuseMaterial = mainMaterial->getMat2();
                }
                
                shininess =
m_hitRecord->getPlane()->getMat()->getMat2()->shinyExp();
                isPhong = true;
                isDiffuse = true;
            }
              
            calcColor();
        }else if(m_hitRecord->getPlane()->getTex() != NULL){
          calcTexColor(m_hitRecord->getPlane());
        }else
        {
          materialType = 0;
          object::Vertex * colV = new object::Vertex(1,1,1);
          scene::Material * diffMat = new scene::Material(colV,0);
          diffuseMaterial = diffMat;
            calcColor(/*1,1,1*/);
          delete diffMat;
        }

        totalColorX =color->x();
        totalColorY =color->y();
        totalColorZ =color->z();
        m_recursiveColors[rec_level]->setRgbF(totalColorX,
                                              totalColorY,
                                              totalColorZ);
        m_hitRecord->setT(0);
        return true;  
    }
    else{
      m_recursiveColors[rec_level]->setRgbF(rBG/(float)(rec_level+1.0),
                                            gBG/(float)(rec_level+1.0),
                                            bBG/(float)(rec_level+1.0));
      return false;
    }
                         
    
   
}



void RayTracer::calcColor(/*float matx, float maty, float matz*/)
{ 
  float red = 0;
  float green = 0;
  float blue = 0;
  
  float nrOfLights = (float)pLights.size();
  for(int lNr = 0; lNr< nrOfLights;++lNr)
  {
    if(pLights[lNr]->isSurface())
      pLights[lNr]->regenerate();
    
    lightPos = pLights[lNr]->pos();
    lightDir->setX(lightPos->x() - m_realPosXYZ[rec_level]->x());
    lightDir->setY(lightPos->y() - m_realPosXYZ[rec_level]->y());
    lightDir->setZ(lightPos->z() - m_realPosXYZ[rec_level]->z());
    lightDir->setH(lightPos->h() - m_realPosXYZ[rec_level]->h());

    lightDir->normalize();
    float newNDotL = surfNorm->dotProd(lightDir);
    
    if(newNDotL >0 && !isShadow(m_realPosXYZ[rec_level],lightPos)){
       float intensity = pLights[lNr]->intensity();
        if(materialType==0 || isDiffuse){ // begin material0 DIFFUSE
          
          float tempRed =
(diffuseMaterial->color()->x()*newNDotL*intensity*pLights[lNr]->color()->x())/
nrOfLights;
          float tempGreen =
(diffuseMaterial->color()->y()*newNDotL*intensity*pLights[lNr]->color()->y())/
nrOfLights;
          float tempBlue =
(diffuseMaterial->color()->z()*newNDotL*intensity*pLights[lNr]->color()->z())/
nrOfLights;
          
          if(m_reflect){ // BEGIN REFLECT
            Ray * ray;

            float NDotC = 2.0*m_cam->direction()->dotProd(surfNorm);
            object::Vertex * tempRhrlp = surfNorm->scaled(NDotC);
            object::Vertex * rVec = m_cam->direction()->minus(tempRhrlp);

            if(m_glossy){
              // w = rvecNormalized
              rVec->normalize();
              float randomX = rand()%100/100.0;
              float randomY = rand()%100/100.0;
              Vertex *tVec = rVec->getGlossy();
              Vertex *uVec = tVec->vecProd(rVec);
              uVec->normalize();
              Vertex *vVec = rVec->vecProd(uVec);
              float uVal = -m_glossyFactor/2.0 + randomX*m_glossyFactor;
              float vVal = -m_glossyFactor/2.0 + randomY*m_glossyFactor;
              Vertex * uScal = uVec->scaled(uVal);
              Vertex * vScal = vVec->scaled(vVal);
              rVec->unNormalize();
              Vertex * temp1 = rVec->plus(uScal);
              Vertex * rAccent = temp1->plus(vScal);
              ray = new Ray(m_realPosXYZ[rec_level],rAccent, true);  /* DOT NOT
              DELETE rAccent (ray does that) */
              delete tVec;
              delete uVec;
              delete vVec;
              delete uScal;
              delete vScal;
              delete temp1;
            }else{
              ray = new Ray(m_realPosXYZ[rec_level],rVec, true); /* DOT NOT
              DELETE RVEC (ray does that) */
            }
            

            ++rec_level;
            
            if(rec_level < MAX_REC_LEVEL && rayColor(ray))
            {
              
              tempRed+= (m_specColor->x() *
                    m_recursiveColors[rec_level]->redF()/nrOfLights);
              tempGreen+= (m_specColor->y()*
                    m_recursiveColors[rec_level]->greenF()/nrOfLights);
              tempBlue+= (m_specColor->z()*
                    m_recursiveColors[rec_level]->blueF()/nrOfLights);
            } else{
              
                tempRed+=     ( m_specColor->x() * rBG/nrOfLights);
                tempGreen +=  ( m_specColor->y() * gBG/nrOfLights);
                tempBlue +=   ( m_specColor->z() * bBG/nrOfLights);
            }
            --rec_level;

            
            delete ray;
            
          } // end reflect
          
          // check if material2
          // =====> * weight
          if(materialType == 2){              
              red += tempRed*mainMaterial->getWeight1();
              green += tempGreen*mainMaterial->getWeight1();
              blue += tempBlue*mainMaterial->getWeight1();
          }
          else{
            red += tempRed;
            green += tempGreen;
            blue += tempBlue;
          }
                    
        } // end material0
        
        
        if(materialType == 1 || isPhong) {
          hVec = m_cam->position()->plus(lightDir);
          hVec->normalize();
          //qDebug("Material 2 HIT..");
          float phong2 = pow(hVec->dotProd(surfNorm),shininess);
          //cout << "phong2 " << phong2 << endl;   
          float tempRed
=(phongMaterial->color()->x()*phong2*pLights[lNr]->color()->x()*intensity)
/nrOfLights;
          float tempGreen
=(phongMaterial->color()->y()*phong2*pLights[lNr]->color()->y()*intensity)
/nrOfLights;
          float tempBlue =
(phongMaterial->color()->z()*phong2*pLights[lNr]->color()->z()*intensity)
/nrOfLights;
          
          
                    
          if(m_reflect){
            //qDebug("Material 2..");
            float NDotC = 2*m_cam->direction()->dotProd(surfNorm);
            object::Vertex * tempRhrlp = surfNorm->scaled(NDotC);
            object::Vertex * rVec = m_cam->direction()->minus(tempRhrlp);

            Ray * ray = new Ray(m_realPosXYZ[rec_level],rVec, true); /* DOT NOT
            DELETE RVEC (ray does that) */

            ++rec_level;
            if(rec_level < MAX_REC_LEVEL && rayColor(ray ))
            {
              tempRed+= m_specColor->x() *
                    m_recursiveColors[rec_level]->redF()/nrOfLights;
              tempGreen+= + m_specColor->y()*
                    m_recursiveColors[rec_level]->greenF()/nrOfLights;
              tempBlue += m_specColor->z()*
                    m_recursiveColors[rec_level]->blueF()/nrOfLights;
            } else{
              tempRed += (m_specColor->x() * rBG/nrOfLights);
              tempGreen += (m_specColor->y() * gBG/nrOfLights);
              tempBlue += (m_specColor->z() * bBG/nrOfLights);
            }
            --rec_level;

            
            delete ray;
          } // end reflect
          
          
           // check if material2
          // =====> * weight
          if(materialType == 2){
              red += tempRed*mainMaterial->getWeight2();
              green += tempGreen*mainMaterial->getWeight2();
              tempBlue += tempBlue*mainMaterial->getWeight2();
          }else{
              red += tempRed;
              green += tempGreen;
              blue += tempBlue;
          }

        }

    } // end shadow test
    
  } 
  
  
  red = max((float)0,red);
  red = min((float)1,red);
  
  green = max((float)0,green);
  green = min((float)1,green);
  
  blue = max((float)0,blue);
  blue = min((float)1,blue);

  color->setX(red);
  color->setY(green);
  color->setZ(blue);
  
}

bool RayTracer::refract(object::Vertex *d, object::Vertex * norm, 
                 float nt, object::Vertex ** t)
{
  cout << "Subrefract 1" << endl;
  float n = 1; // refractionIndex of air: 1 NOT IMPLEMENTED
  float DDotN = d->dotProd(norm);
  Vertex * sub1 = norm->scaled(DDotN); // to delete
  Vertex * sub2 = d->minus(sub1); // to delete
  Vertex * big1 = sub2->scaled(1.0/nt); // to delete
  
  float sub3 = 1.0 - (1.0 - DDotN*DDotN)/(nt*nt);
  
  if(sub3 < 0){
    delete sub1;
    delete sub2;
    delete big1;
    return false; // TOTAL INTERNAL REFLECTION
  }
  
  cout << "Subrefract 2" << endl;
  
  float sub4 = sqrt(sub3);
  Vertex * big2 = norm->scaled(sub4);
  *t = big1->minus(big2);
  
  delete sub1;
  delete sub2;
  delete big1;
  delete big2;
  return true;
}

void RayTracer::calcTexColor(Plane * plane)
{
  
  ObjectTexture * objCA = plane->getTextureCoord(0);
  ObjectTexture * objCB = plane->getTextureCoord(1);
  ObjectTexture * objCC = plane->getTextureCoord(2);
  
  // u
  float u = objCA->u()    + m_hitRecord->beta()*   (objCB->u() - objCA->u()) 
                          + m_hitRecord->gamma() * (objCC->u() - objCA->u());
  // v
  float v = objCA->v()    + m_hitRecord->beta()*   (objCB->v() - objCA->v()) 
                          + m_hitRecord->gamma() * (objCC->v() - objCA->v());
                          
              
  int textwidth = (float)plane->getTex()->width()*u;
  int textheight = (float)plane->getTex()->height()*v;
  
  int repeat = plane->getTex()->getRepeat();
  
  QColor rgbtemp = 
plane->getTex()->getColor((textwidth*repeat)%plane->getTex()->width(),
(textheight*repeat)%plane->getTex()->height());
  
  float matx = rgbtemp.redF();
  float maty = rgbtemp.greenF();
  float matz = rgbtemp.blueF();
  
  color->setX(matx);
  color->setY(maty);
  color->setZ(matz);
  
  float red = 0;
  float green = 0;
  float blue = 0;
  float nrOfLights = (float)pLights.size();
  for(int lNr = 0; lNr< pLights.size();++lNr)
  {

    lightPos = pLights[lNr]->pos();
    
    lightDir->setX(lightPos->x() - m_realPosXYZ[rec_level]->x());
    lightDir->setY(lightPos->y() - m_realPosXYZ[rec_level]->y());
    lightDir->setZ(lightPos->z() - m_realPosXYZ[rec_level]->z());
    lightDir->setH(lightPos->h() - m_realPosXYZ[rec_level]->h());

    float newNDotL = 0;
    if(!isShadow(m_realPosXYZ[rec_level],lightPos)){
        lightDir->normalize();

        // phong shading
        hVec = m_cam->position()->plus(lightDir);
        hVec->normalize();
       
        
        newNDotL = surfNorm->dotProd(lightDir);
        newNDotL = max((float)0,newNDotL);
        
        float intensity = pLights[lNr]->intensity();
        red+= (matx*newNDotL*intensity) /nrOfLights;
        green +=(maty*newNDotL*intensity) /nrOfLights;
        blue += (matz*newNDotL*intensity) /nrOfLights;
        
        
    }               
  } 
  
  red = max((float)0,red);
  red = min((float)1,red);
  
  green = max((float)0,green);
  green = min((float)1,green);
  
  blue = max((float)0,blue);
  blue = min((float)1,blue);

  color->setX(red);
  color->setY(green);
  color->setZ(blue);
}



}